/*
 * ConnectionManager.cpp
 *
 *  Created on: Nov 21, 2014
 *      Author: toti
 */

#include <Connectivity/ConnectionManager.hpp>

ConnectionManager::ConnectionManager(UserInterface *userInterface) {

	this->config = Config::instance();

	this->respondToDiscoveryRequests = true;

	this->inboundMessagePackageList = new std::list<MessagePackage*>();
	this->outboundMessagePackageList = new std::list<MessagePackage*>();

	this->parentPort = "";
	this->parentAddress = "";

	/*
	 * Working connections
	 */
	this->parentLocalNode = 0;
	this->localServerTcpServer = 0;
	this->localClientList = 0;
	this->localServerList = 0;

	/*
	 * UI
	 */
	this->userInterface = userInterface;

	/*
	 * Peer discovery stuff
	 */
	this->peerDiscovery = new PeerDiscovery();


	/*
	 * IO Multiplexer
	 */
	this->epoll = new EPoll(32);






}


void ConnectionManager::init() {

	localClientList = new std::list<LocalNode*>();
	localServerList = new std::list<LocalNode*>();

	localServerTcpServer = new TCPServer(Config::instance()->getInterfaceAddress(), Config::instance()->getClientPort());
	epoll->addFd(localServerTcpServer->getSock(), localServerTcpServer);

	/*
	 * Add UI to epoll
	 */
	epoll->addFd(userInterface->getInputFileDescriptor(), (void*) userInterface);

	localClientList->push_back(userInterface);

	ClientMessagePackage *clientMessagePackage = new ClientMessagePackage(userInterface);
	clientMessagePackage->setMessageType(ClientMessagePackage::TYPE_REGISTRATION);

	inboundMessagePackageList->push_back((MessagePackage*) clientMessagePackage);

	/*
	 * Trigger peer discovery
	 */
	peerDiscovery->initPeerDiscovery(epoll);
	peerDiscovery->sendPeerDiscoveryRequest();
}

bool ConnectionManager::openParentServerConnection(std::string &port, std::string &address) {

	TCPClient tcpClient(Config::instance()->getInterfaceAddress());
	TCPConnection *tcpConnection = tcpClient.openConnection(port, address);

	if (tcpConnection) {

		parentAddress = address;
		parentPort = port;

		LocalServer *localServer = new LocalServer(tcpConnection);
		parentLocalNode = localServer;

		localServerList->push_back(localServer);

		epoll->addFd(tcpConnection->getSock(), localServer);

		ServerMessagePackage *serverMessagePackage = new ServerMessagePackage(localServer);
		serverMessagePackage->setMessageType(ServerMessagePackage::TYPE_SERVER_REGISTRATION);
		serverMessagePackage->setSender(Config::instance()->getClientName());
		serverMessagePackage->setParameter(ServerMessagePackage::SERVER_REGISTRATION__SERVER_NAME, Config::instance()->getClientName());
		serverMessagePackage->generateMessageString();

		outboundMessagePackageList->push_back((MessagePackage*) serverMessagePackage);

		serverMessagePackage = new ServerMessagePackage(localServer);
		serverMessagePackage->setMessageType(ServerMessagePackage::TYPE_REQUEST_NICK);
		serverMessagePackage->setSender(Config::instance()->getClientName());
		serverMessagePackage->setParameter(ServerMessagePackage::REQUEST_NICK__NICK, Config::instance()->getClientName());
		serverMessagePackage->setParameter(ServerMessagePackage::REQUEST_NICK__NEW_NICK, Config::instance()->getClientName());
		serverMessagePackage->generateMessageString();

		outboundMessagePackageList->push_back((MessagePackage*) serverMessagePackage);

		serverMessagePackage = new ServerMessagePackage(localServer);
		serverMessagePackage->setMessageType(ServerMessagePackage::TYPE_UPDATEREQUEST_SERVER_LIST);
		serverMessagePackage->setSender(Config::instance()->getClientName());
		serverMessagePackage->generateMessageString();

		outboundMessagePackageList->push_back((MessagePackage*) serverMessagePackage);

		serverMessagePackage = new ServerMessagePackage(localServer);
		serverMessagePackage->setMessageType(ServerMessagePackage::TYPE_UPDATEREQUEST_CLIENT_LIST);
		serverMessagePackage->setSender(Config::instance()->getClientName());
		serverMessagePackage->generateMessageString();

		outboundMessagePackageList->push_back((MessagePackage*) serverMessagePackage);

		serverMessagePackage = new ServerMessagePackage(localServer);
		serverMessagePackage->setMessageType(ServerMessagePackage::TYPE_UPDATEREQUEST_CHANNEL_LIST);
		serverMessagePackage->setSender(Config::instance()->getClientName());
		serverMessagePackage->generateMessageString();

		outboundMessagePackageList->push_back((MessagePackage*) serverMessagePackage);

		serverMessagePackage = new ServerMessagePackage(localServer);
		serverMessagePackage->setMessageType(ServerMessagePackage::TYPE_UPDATEREQUEST_CLIENT_LIST);
		serverMessagePackage->setSender(Config::instance()->getClientName());
		serverMessagePackage->generateMessageString();

		outboundMessagePackageList->push_back((MessagePackage*) serverMessagePackage);

		return true;

	} else {

		std::cout << Config::instance()->getClientName();
		std::cout << ": Peer refused connection " << address << " " << port << std::endl;

		return false;
	}
}

/****************************************************************************
 *
 *
 *
 * Data handler functions
 *
 *
 *
 ***************************************************************************/

void ConnectionManager::handleInputData() {

	int n = epoll->wait();

	for (int i = 0; i < n; i++) {

		struct epoll_event *event = epoll->getEventBuffer(i);

		if ((event->events & EPOLLERR) || (event->events & EPOLLHUP) || (event->events & EPOLLRDHUP)) {

			handleExistingLocalNodeError((LocalNode*) event->data.ptr);

		} else if (event->events & EPOLLIN) {

			if (event->data.ptr == localServerTcpServer) {

				handleNewLocalServerConnection((TCPServer*) localServerTcpServer);

			} else if (event->data.ptr == peerDiscovery->getMulticastUDPServer()) {

				handlePeerDiscoveryRequest();

			} else if (event->data.ptr == peerDiscovery->getUnicastUDPServer()) {

				handlePeerDiscoveryResponse();

			} else if (event->data.ptr == userInterface) {

				handleUserInterfaceData((LocalNode*) event->data.ptr);

			} else {

				handleExistingLocalNodeConnection((LocalNode*) event->data.ptr);

			}

		}

	}

}

void ConnectionManager::handleOutputData() {

	while (outboundMessagePackageList->size() > 0) {

		MessagePackage *messagePackage = outboundMessagePackageList->front();
		outboundMessagePackageList->pop_front();

		LocalNode *localNode = messagePackage->getLocalNode();

		if (localNode == userInterface) {

			if (messagePackage->getMessagePackageType() == MessagePackage::CLIENT_MESSAGE_PACKAGE) {
				((UserInterface*) localNode)->handleOutputData(messagePackage);
			}
			delete (messagePackage);

		} else if (localNode) {

			messagePackage->generateMessageString();
			localNode->handleOutputData(messagePackage);
			delete (messagePackage);

		} else {

			pushInboundMessagePackageList(messagePackage);

		}

	}

}

void ConnectionManager::handlePeerDiscoveryRequest() {

	PeerDiscovery::PeerDiscoveryRequestData_t peerDiscoveryRequestData;

	peerDiscovery->receivePeerDiscoveryRequest(&peerDiscoveryRequestData);

	if((int)localServerList->size() < Config::instance()->getConnectionNumber()){

		peerDiscovery->replyPeerDiscoveryRequest(&peerDiscoveryRequestData);

	}

}

void ConnectionManager::handlePeerDiscoveryResponse() {


	PeerDiscovery::PeerDiscoveryResponseData_t *peerDiscoveryResponseData;
	peerDiscoveryResponseData = peerDiscovery->receivePeerDiscoveryResponseData();

	if(peerDiscoveryResponseData && parentLocalNode == 0){

		char receivedAddress[INET6_ADDRSTRLEN];
		uint32_t receivedPort;

		inet_ntop(AF_INET6, &(peerDiscoveryResponseData->address), receivedAddress, INET6_ADDRSTRLEN);
		receivedPort = ntohl(peerDiscoveryResponseData->port);

		std::stringstream receivedPortStringStream;
		receivedPortStringStream << receivedPort;

		std::string receivedAddressString(receivedAddress);
		std::string receivedPortString = receivedPortStringStream.str();

		std::cout << Config::instance()->getClientName();
		std::cout << ": Connecting to parent " << receivedAddressString << " " << receivedPortString << std::endl;

		openParentServerConnection(receivedPortString, receivedAddressString);

	}

}

void ConnectionManager::handleExistingLocalNodeError(LocalNode *localNode) {

	std::string hostname;

	localNode->getTcpConnection()->getHostname(hostname);
	localServerList->remove(localNode);
	epoll->remFd(localNode->getTcpConnection()->getSock());
	delete(localNode);

	std::cout << Config::instance()->getClientName();
	std::cout << ": Remove local node " << hostname << std::endl;

	if(!localServerTcpServer->isServerOpen()){
		localServerTcpServer->init();
		epoll->addFd(localServerTcpServer->getSock(), (void*)localServerTcpServer);
	}

}

void ConnectionManager::handleNewLocalServerConnection(TCPServer *tcpServer) {

	if (tcpServer->isServerOpen()) {

		TCPConnection *tcpConnection = localServerTcpServer->waitForConnection();
		LocalServer *localServer = new LocalServer(tcpConnection);

		epoll->addFd(tcpConnection->getSock(), localServer);
		localServerList->push_back(localServer);

		if ((int)localServerList->size() >= Config::instance()->getConnectionNumber()) {

			tcpServer->closeServer();
			respondToDiscoveryRequests = false;

		}

	}
}

void ConnectionManager::handleExistingLocalNodeConnection(LocalNode *localNode) {

	if (!localNode->handleInputData(inboundMessagePackageList)) {

		handleExistingLocalNodeError(localNode);

	}

}

void ConnectionManager::handleUserInterfaceData(LocalNode *localNode) {

	((UserInterface*) localNode)->handleInputData(inboundMessagePackageList);

}



void ConnectionManager::closeAllConnections() {

	for (std::list<LocalNode*>::iterator it = localServerList->begin(); it != localServerList->end(); ++it) {

		if (*it) {
			shutdown((*it)->getTcpConnection()->getSock(), SHUT_RD);
		}
	}

}

void ConnectionManager::closeServerListening() {

}

/****************************************************************************
 *
 *
 *
 * Misc functions
 *
 *
 *
 ***************************************************************************/

void ConnectionManager::setRespondToDiscoveryRequests(bool respond){
	this->respondToDiscoveryRequests = respond;
}

void ConnectionManager::setLocalParentServer(LocalNode *localNode){
	this->parentLocalNode = localNode;
}

PeerDiscovery *ConnectionManager::getPeerDiscovery(){
	return peerDiscovery;
}

std::string &ConnectionManager::getParentAddress(){
	return parentAddress;
}

std::string &ConnectionManager::getParentPort(){
	return parentPort;
}

LocalNode *ConnectionManager::getUserInterface() {
	return userInterface;
}

LocalNode *ConnectionManager::getLocalParentServer() {
	return parentLocalNode;
}

std::list<LocalNode*> *ConnectionManager::getLocalClientList() {
	return localClientList;
}

std::list<LocalNode*> *ConnectionManager::getLocalServerList() {
	return localServerList;
}

bool ConnectionManager::isMessagesAvailable() {
	return (outboundMessagePackageList->size() || inboundMessagePackageList->size());
}

void ConnectionManager::pushOutboundMessagePackageList(MessagePackage *messagePackage) {
	outboundMessagePackageList->push_back(messagePackage);
}

void ConnectionManager::pushInboundMessagePackageList(MessagePackage *messagePackage) {
	inboundMessagePackageList->push_back(messagePackage);
}

MessagePackage *ConnectionManager::popInboundMessagePackageList() {

	MessagePackage *messagePackage = 0;

	if (inboundMessagePackageList->size()) {
		messagePackage = inboundMessagePackageList->front();
		inboundMessagePackageList->pop_front();
	}

	return messagePackage;
}


